package com.lcz.audioplayer.view;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.widget.OverScroller;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;

import com.lcz.audioplayer.R;

import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;

/**
 * 作者:     刘传政
 * 创建时间:  9:36 2024/4/23 0023
 * QQ:      1052374416
 * 电话:     18501231486
 * 描述:     有滑动，拖动，回弹效果
 */
public class LrcView extends View {
    final String TAG = LrcView.class.getSimpleName();
    final String DEFAULT_CONTENT = "还没有歌词";
    private List<LrcBean> lrcBeans;
    private TextPaint mTextPaint;
    private float mLrcTextSize;
    private String mDefaultContent = DEFAULT_CONTENT;
    private int mCurrentLine;
    private float mLrcLineSpaceHeight;
    private float mOffset;//滑动，拖动，动画，都是通过控制它，影响绘制的y坐标
    private int mScaledTouchSlop;
    private VelocityTracker mVelocityTracker;
    private int mMaximumFlingVelocity;//系统的最大滑动速度
    private int mMinimumFlingVelocity;//系统的最小滑动速度
    private OverScroller mOverScroller;
    //是否拖拽中，否的话响应onClick事件
    private boolean isDragging;
    private boolean isShowingTimeIndicator;//是否正在展示指示器
    private Drawable mPlayDrawable;
    private SimpleDateFormat mMinFormat = new SimpleDateFormat("mm:ss");
    private HashMap<String, StaticLayout> mStaticLayoutMap = new HashMap<>();
    Rect mPlayRect;
    private boolean isUserTouch;
    private boolean isAutoAdjustPosition = true;
    private int mTouchDelay = 3000;

    public LrcView(Context context) {
        this(context, null);
    }

    public LrcView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LrcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        mMaximumFlingVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity();
        mMinimumFlingVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
        mOverScroller = new OverScroller(getContext(), new DecelerateInterpolator());
        mOverScroller.setFriction(0.1f);//摩擦系数
        mPlayDrawable = ContextCompat.getDrawable(getContext(), R.mipmap.play_normal);
        mLrcTextSize = sp2px(getContext(), 15);
        mLrcLineSpaceHeight = dp2px(getContext(), 20);
        mTextPaint = new TextPaint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setTextSize(mLrcTextSize);

        mIndicatorPaint = new Paint();
        mIndicatorPaint.setAntiAlias(true);
        mIndicatorPaint.setStrokeWidth(dp2px(getContext(), 0.5f));
        mIndicatorPaint.setColor(Color.GREEN);
        mIndicatorPaint.setTextSize(sp2px(getContext(), 12));

    }

    public void setLrcData(List<LrcBean> lrcBeans) {
        resetView();
        this.lrcBeans = lrcBeans;
        invalidate();
    }
    public void resetView() {
        if (lrcBeans != null) {
            lrcBeans.clear();
        }
        mStaticLayoutMap.clear();
        mCurrentLine = 0;
        mOffset = 0;
        isUserTouch = false;
        isDragging = false;
        removeCallbacks(mScrollRunnable);
        removeCallbacks(mHideIndicatorRunnable);
        invalidate();
    }

    private int getWidthConsiderPadding() {
        return getWidth() - getPaddingLeft() - getPaddingRight();
    }

    private int getHeightConsiderPadding() {
        return getWidth() - getPaddingTop() - getPaddingBottom();
    }

    private boolean isLrcEmpty() {
        return lrcBeans == null || lrcBeans.isEmpty();
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);

        if (isLrcEmpty()) {
            drawEmptyView(canvas);
            return;
        }
        int indicatePosition = getIndicatePosition();
        drawLrcs(canvas, indicatePosition);
        drawIndicator(canvas, indicatePosition);
    }

    float mIconHeight = dp2px(getContext(), 30);
    float mIconWidth = dp2px(getContext(), 30);
    private Paint mIndicatorPaint;

    /**
     * 画指示器
     *
     * @param canvas
     * @param indicatePosition
     */
    private void drawIndicator(Canvas canvas, int indicatePosition) {
        if (isShowingTimeIndicator) {


            if (mPlayRect == null) {
                mPlayRect = new Rect();
            }
            mPlayRect.left = (int) getPaddingLeft();
            mPlayRect.top = (int) (getHeight() / 2 - mIconWidth / 2);
            mPlayRect.right = (int) (mPlayRect.left + mIconWidth);
            mPlayRect.bottom = (int) (mPlayRect.top + mIconHeight);
            mPlayDrawable.setBounds(mPlayRect);
            mPlayDrawable.draw(canvas);
            long time = lrcBeans.get(indicatePosition).time;
            String format = mMinFormat.format(time);
            float timeWidth = mIndicatorPaint.measureText(format);
            mIndicatorPaint.setColor(Color.GREEN);
            canvas.drawLine(mPlayRect.right, getHeight() / 2f,
                    getWidth() - getPaddingRight() - timeWidth * 1.3f, getHeight() / 2f, mIndicatorPaint);
            int baseX = (int) (getWidth() - getPaddingRight() - timeWidth * 1.1f);
            float baseline = getHeight() / 2f - (mIndicatorPaint.descent() - mIndicatorPaint.ascent()) / 2 - mIndicatorPaint.ascent();
            mIndicatorPaint.setColor(Color.BLUE);
            canvas.drawText(format, baseX, baseline, mIndicatorPaint);
        }
    }

    public int getIndicatePosition() {
        int pos = 0;
        float min = Float.MAX_VALUE;
        //itemOffset 和 mOffset 最小即当前位置
        for (int i = 0; i < lrcBeans.size(); i++) {
            float offsetY = getItemOffsetY(i);
            float abs = Math.abs(offsetY - mOffset);
            if (abs < min) {
                min = abs;
                pos = i;
            }
        }
        return pos;
    }

    /**
     * 暂停（手动滑动歌词后，不再自动回滚至当前播放位置）
     */
    public void pause() {
        isAutoAdjustPosition = false;
        invalidate();
    }

    /**
     * 恢复（继续自动回滚）
     */
    public void resume() {
        isAutoAdjustPosition = true;
        ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
        invalidate();
    }

    private void drawLrcs(Canvas canvas, int indicatePosition) {
        mTextPaint.setTextSize(mLrcTextSize);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        float x = getWidth() / 2f;
        float y = getHeight() / 2f;
        for (int i = 0; i < lrcBeans.size(); i++) {
            if (i > 0) {
                //算出一行高度应该加多少
                y += getTextHeight(lrcBeans.get(i).text) / 2 + mLrcLineSpaceHeight + getTextHeight(lrcBeans.get(i - 1).text) / 2;
            }
            if (mCurrentLine == i) {
                mTextPaint.setColor(Color.RED);
                mTextPaint.setFakeBoldText(true);
            } else if (indicatePosition == i && isShowingTimeIndicator) {
                mTextPaint.setColor(Color.YELLOW);
                mTextPaint.setFakeBoldText(true);
            } else {
                mTextPaint.setFakeBoldText(false);
                mTextPaint.setColor(Color.GRAY);
            }
            drawLrc(canvas, x, y, lrcBeans.get(i).text);

        }
    }

    private void drawLrc(Canvas canvas, float x, float y, String text) {
        if (y - mOffset < -50 || y - mOffset > getHeight() + 50) {
            //超出上下两边的部分，用户看不到，就不画了。节约资源。50是随便定的一个值，否则这个50的计算很复杂
            return;
        }

        mTextPaint.setTextSize(mLrcTextSize);
        StaticLayout staticLayout = mStaticLayoutMap.get(text);
        if (staticLayout == null) {
            staticLayout = new StaticLayout(text, mTextPaint,
                    getWidthConsiderPadding(), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
            mStaticLayoutMap.put(text, staticLayout);
        }
        canvas.save();
        canvas.translate(x, y - staticLayout.getHeight() / 2 - mOffset);
        staticLayout.draw(canvas);
        canvas.restore();
    }

    private Runnable mScrollRunnable = new Runnable() {
        @Override
        public void run() {
            isUserTouch = false;
            scrollToPosition(mCurrentLine);
        }
    };

    private void scrollToPosition(int linePosition) {
        float scrollY = getItemOffsetY(linePosition);
        final ValueAnimator animator = ValueAnimator.ofFloat(mOffset, scrollY);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mOffset = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.setDuration(300);
        animator.start();
    }

    private float getItemOffsetY(int linePosition) {
        float tempY = 0;
        for (int i = 1; i <= linePosition; i++) {
            tempY += (getTextHeight(lrcBeans.get(i - 1).text) + getTextHeight(lrcBeans.get(i).text)) / 2 + mLrcLineSpaceHeight;
        }
        return tempY;
    }

    public void updateProgressPosition(long progressPosition) {
        if (isLrcEmpty()) {
            return;
        }
        int linePosition = getLinePositioin(progressPosition);
        if (mCurrentLine != linePosition) {
            mCurrentLine = linePosition;
            if (isUserTouch) {
                invalidate();
                return;
            }
            ViewCompat.postOnAnimation(LrcView.this, mScrollRunnable);
        }
    }

    private int getLinePositioin(long progressPosition) {
        int linePos = 0;
        for (int i = 0; i < lrcBeans.size(); i++) {
            LrcBean lrcBean = lrcBeans.get(i);
            if (progressPosition >= lrcBean.time) {
                if (i == lrcBeans.size() - 1) {
                    linePos = lrcBeans.size() - 1;
                } else if (progressPosition < lrcBeans.get(i + 1).time) {
                    linePos = i;
                    break;
                }
            }
        }

        return linePos;
    }

    private float getTextHeight(String text) {
        StaticLayout staticLayout = mStaticLayoutMap.get(text);
        if (staticLayout == null) {
            staticLayout = new StaticLayout(text, mTextPaint,
                    getWidthConsiderPadding(), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
            mStaticLayoutMap.put(text, staticLayout);
        }
        return staticLayout.getHeight();
    }

    private void drawEmptyView(Canvas canvas) {
        mTextPaint.setTextAlign(Paint.Align.CENTER);//文字相对于绘制起点的对齐方式
        mTextPaint.setColor(Color.BLACK);
        mTextPaint.setTextSize(sp2px(getContext(), 15));
        canvas.save();
        StaticLayout staticLayout = new StaticLayout(mDefaultContent, mTextPaint,
                getWidth(), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
        canvas.translate(getWidth() / 2f, getHeight() / 2f);
        staticLayout.draw(canvas);
        canvas.restore();
    }

    private float mLastMotionX;
    private float mLastMotionY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isLrcEmpty()) {
            return super.onTouchEvent(event);
        }
        if (mVelocityTracker == null) {
            //速度追踪器
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                removeCallbacks(mScrollRunnable);
                removeCallbacks(mHideIndicatorRunnable);
                isUserTouch = true;
                if (!mOverScroller.isFinished()) {
                    mOverScroller.abortAnimation();
                }
                mLastMotionX = event.getX();
                mLastMotionY = event.getY();
                isDragging = false;
                break;
            case MotionEvent.ACTION_MOVE:
                float moveY = event.getY() - mLastMotionY;
                if (Math.abs(moveY) > mScaledTouchSlop) {
                    isDragging = true;
                    isShowingTimeIndicator = true;
                }
                if (isDragging) {
                    float maxHeight = getItemOffsetY(lrcBeans.size() - 1);

                    if (mOffset < 0 || mOffset > maxHeight) {
                        moveY /= 4f;
                    }
                    mLastMotionY = event.getY();
                    mOffset -= moveY;

                    invalidate();
                }


                break;
            case MotionEvent.ACTION_UP:
                handleActionUp(event);
                break;
        }
        return true;
    }

    private Runnable mHideIndicatorRunnable = new Runnable() {
        @Override
        public void run() {
            isShowingTimeIndicator = false;
            postInvalidate();
        }
    };
    private OnPlayIndicatorLineListener mOnPlayIndicatorLineListener;

    public void setOnPlayIndicatorLineListener(OnPlayIndicatorLineListener onPlayIndicatorLineListener) {
        mOnPlayIndicatorLineListener = onPlayIndicatorLineListener;
    }

    public interface OnPlayIndicatorLineListener {
        void onPlay(long time, String content);
    }

    private boolean isClickPlayButton(MotionEvent event) {
        float left = mPlayRect.left;
        float right = mPlayRect.right;
        float top = mPlayRect.top;
        float bottom = mPlayRect.bottom;
        float x = event.getX();
        float y = event.getY();
        return mLastMotionX > left && mLastMotionX < right && mLastMotionY > top && mLastMotionY < bottom //触摸起点在图标上
                && x > left && x < right && y > top && y < bottom;//触摸终点点在图标上
    }
    private void releaseVelocityTracker() {
        if (null != mVelocityTracker) {
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }
    private void handleActionUp(MotionEvent event) {

        this.postDelayed(mHideIndicatorRunnable, 3000);//3秒后自动消失指示器
        if (isShowingTimeIndicator && mPlayRect != null && isClickPlayButton(event)) {
            isShowingTimeIndicator = false;
            postInvalidate();
            //点击了指示器。通知外界更新进度
            if (mOnPlayIndicatorLineListener != null) {
                int indicatePosition = getIndicatePosition();
                mOnPlayIndicatorLineListener.onPlay(lrcBeans.get(indicatePosition).time,
                        lrcBeans.get(indicatePosition).text);
            }
        }
        mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
        float yVelocity = mVelocityTracker.getYVelocity();
        float absYVelocity = Math.abs(yVelocity);
        if (absYVelocity > mMinimumFlingVelocity) {
            mOverScroller.fling(
                    0,
                    (int) mOffset,
                    0,
                    (int) (-yVelocity),
                    0,
                    0,
                    0,
                    (int) getItemOffsetY(lrcBeans.size() - 1),
                    0,
                    300);
            Log.i(TAG, "开始滑动mOffset：" + mOffset + "速度：" + yVelocity);
            /*//这个方法就是数学计算器，目的就是要一个滑动变化的y值。100~-1000回弹到-500  100-1000回弹到500
            mOverScroller.fling(
                    0,
                    100,
                    0,
                    (int) (-yVelocity),
                    0,
                    0,
                    -500,
                    500,
                    0,
                    500);*/
            invalidate();
        }
        if (mOverScroller.isFinished()) {
            //filing停止了，我们再处理越界。filing时会自动回弹处理越界
            if (overScrolled() && mOffset < 0) {
                //拖动越界了，并且是上边越界。那就平稳的定位到起始位置
                scrollToPosition(0);
            }
            if (overScrolled() && mOffset > getItemOffsetY(lrcBeans.size() - 1)) {
                //拖动越界了，并且是下边越界。那就平稳的定位到末尾位置
                scrollToPosition(lrcBeans.size() - 1);
            }
        }
        if (isAutoAdjustPosition) {
            ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);
        }
        releaseVelocityTracker();

    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mOverScroller.computeScrollOffset()) {
            //还在执行
            mOffset = mOverScroller.getCurrY();
//            Log.i(TAG, "" + mOffset);
            invalidate();
        } else {
        }
    }

    /**
     * 滑动越界了
     *
     * @return
     */
    private boolean overScrolled() {
        return mOffset > getItemOffsetY(lrcBeans.size() - 1) || mOffset < 0;
    }

    public int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }

    public int sp2px(Context context, float spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, context.getResources().getDisplayMetrics());
    }
}
